//
//  VWM3u8Parser.swift
//  VWM3u8Downloader
//
//  Created by pro5 on 2019/1/21.
//

import Foundation
import Alamofire

open class M3u8Parser {
    /// 标识符
    let identifier: String
    /// 本地下载位置
    let downloadPath: URL
    
    struct Output {
        let tsList: [M3u8TsModel]
        /// m3u8内容
        let m3u8Content: String
    }
    /// identifier为下载的目录名
    init(identifier: String, downloadPath: URL) {
        self.identifier = identifier
        self.downloadPath = downloadPath
    }
    deinit {
        debugPrint("下载解析 销毁")
    }
    /// 解析并下载m3u8文件，返回ts列表由外部去下载，外部调用该方法即可，注意回调会在后台线程
    func downloadM3u8File(_ url: String, completion: @escaping (Result<[M3u8TsModel], Error>) -> Void) {
        self.parse(url) { [weak self] result in
            switch result {
            case let .success(output):
                self?.createLocalM3U8file(output.m3u8Content, tsList: output.tsList)
                completion(.success(output.tsList))
            case let .failure(error):
                completion(.failure(error))
            }
        }
    }
    /// 解析m3u8
    func parse(_ url: String, completion: @escaping (Result<Output, Error>) -> Void) {
        DispatchQueue.global(qos: .background).async {
            if !(url.hasPrefix("http://") || url.hasPrefix("https://")) {
                print("Invalid http URL.(无效的URL链接)")
                return completion(.failure(self.error("无效的URL链接")))
            }
            do {
                let time = Date()
                debugPrint("解析开始：\(-time.timeIntervalSinceNow)")
                /// 尝试将layer url 解析成字符串
                let layerM3u8Content = try String(contentsOf: URL(string: url)!, encoding: .utf8)
                if layerM3u8Content.isEmpty {
                    return completion(.failure(self.error("m3u8内容为空")))
                } else {
                    debugPrint("解析取到m3u8内容：\(-time.timeIntervalSinceNow)")
                    let firstResult = self.parseFirstLayerM3u8(layerM3u8Content, url: url)
                    switch firstResult {
                    case .success(_):
                        debugPrint("解析第一层成功：\(-time.timeIntervalSinceNow)")
                        return completion(firstResult)
                    default:
                        debugPrint("解析第一层失败：\(-time.timeIntervalSinceNow)")
                        // 将layerUrl 切片，为后面拼接用
                        let m3u8UrlRoots = self.urlRoots(for: url)
                        let secondResult = self.parseSecondLayerM3u8(layerM3u8Content, m3u8UrlRoots: m3u8UrlRoots)
                        debugPrint("解析第二层结束：\(-time.timeIntervalSinceNow)")
                        completion(secondResult)
                    }
                }
            } catch let error {
                print(error.localizedDescription)
                print("<Layer> m3u8 file content first read error. \(url)")
                return completion(.failure(error))
            }
        }
    }
    func error(_ message: String) -> Error {
        return NSError(domain: "VWM3u8Parser", code: 0, userInfo: [NSLocalizedDescriptionKey: message])
    }
    /// 解析第一层m3u8
    func parseFirstLayerM3u8(_ content: String, url: String) -> Result<Output, Error> {
        /// 第一层就解析到了ts流
        if content.range(of: "#EXTINF:") != nil {
            print("<Layer> m3u8 can be parse, start parsing.(m3u8解析 - 修成正果)")
            let tsUrlRoots = self.urlRoots(for: url)
            let tsList = self.sepalateRealM3u8List(content, url, tsUrlRoots: tsUrlRoots)
            if tsList.count > 0 {
                return .success(.init(tsList: tsList, m3u8Content: content))
            }else{
                return .failure(self.error("m3u8解析不到可用的ts链接"))
            }
        }else{
            return .failure(self.error("没有#EXTINF标签"))
        }
    }
    func parseSecondLayerM3u8(_ content: String, m3u8UrlRoots: [String]) -> Result<Output, Error> {
        /// 第一层解析出来没有ts流，说明有2层，解析第二层
        if content.range(of: "#EXT-X-STREAM-INF:") != nil {
            print("<Layer> m3u8 can not be parse, parse again.")
            var m3u8String = ""
            let lastM3u8pes = content.components(separatedBy: "\n")
            /// 获取带.m3u8后缀的切片
            for stringPs in lastM3u8pes {
                if stringPs.contains(".m3u8") {
                    m3u8String = stringPs
//                    self.lastM3u8File = stringPs
                }
            }
            /// 拼接批量二次解析需要的第二层m3u8Url
            if !m3u8String.isEmpty {
                var secondParseUrls: [String] = []
                for header in m3u8UrlRoots {
                    var realM3u8Url = ""
                    if m3u8String.hasPrefix("/") {
                        realM3u8Url = String(format: "%@%@", header, m3u8String)
                    } else {
                        realM3u8Url = String(format: "%@/%@", header, m3u8String)
                    }
                    print("secondParseUrl m3u8：\(realM3u8Url)")
                    secondParseUrls.append(realM3u8Url)
                }
                /// 第二次解析
                return self.parseDepthM3u8(secondParseUrls)
            }else{
                return .failure(self.error("没有第二层m3u8链接"))
            }
        }else{
            return .failure(self.error("没有#EXT-X-STREAM-INF标签"))
        }
    }
    
    /// 第二次解析, 尝试去解析每一个可能的Url,只要解析到，并且包含ts流,取出ts
    func parseDepthM3u8(_ urls: [String]) -> Result<Output, Error> {
        for depthParseUrl in urls {
            if let depthM3u8Content = try? String(contentsOf: URL(string: depthParseUrl)!, encoding: .utf8) {
                // 解析到
                if !depthM3u8Content.isEmpty {
                    let result = self.parseFirstLayerM3u8(depthM3u8Content, url: depthParseUrl)
                    switch result {
                    case .success(_):
                        return result
                    default:
                        break
                    }
                }
            } else {
                print("<Depth> m3u8 parse failed!,Invalid m3u8 URL :\(depthParseUrl) ")
            }
        }
        // 解析失败
        print("<Depth> m3u8 parse failed!")
        return .failure(self.error("第二层m3u8解析不到可用的ts链接"))
    }
}

// MARK: - Private funcs
extension M3u8Parser {
    /// 切片Url
    private func urlRoots(for url: String) -> [String] {
        let headerPaths = url.components(separatedBy: "/")
        if headerPaths.count <= 3 {
            return []
        }
        var result: [String] = []
        var headerUtlPath = ""
        if let lastStr = headerPaths.last {
            if var depthM3u8Header = url.components(separatedBy: lastStr).first {
                if depthM3u8Header.hasSuffix("/") {
                    depthM3u8Header.remove(at: depthM3u8Header.index(before: depthM3u8Header.endIndex))
                }
                result.append(depthM3u8Header)
                headerUtlPath = depthM3u8Header
            }
        }
        result.append(contentsOf: self.urlRoots(for: headerUtlPath))
        return result
    }
}

// MARK: - Privite Funcs
extension M3u8Parser {
    
    /// 下载秘钥
    ///
    /// - Parameter m3u8Content: 带ts列表的有效 m3u8Content
    private func downLoadKeyWith(_ m3u8Content: String, tsUrlRoots: [String]) {
        if !m3u8Content.contains("#EXT-X-KEY:") { return }
        var keySttr = ""
        // 用正则表达式取出秘钥所在 url
        keySttr = self.regexGetSub(pattern: "#EXT-X-KEY:.*\\bURI=\"([^\"]*)\"", str: m3u8Content)

        if !keySttr.isEmpty {
            print("DownLoad KEY file by URI = \(keySttr) ")
            if let dataKey = tryToGetKeyData(keySttr, tsUrlRoots: tsUrlRoots) {
                let filePath = self.downloadPath.appendingPathComponent("key.key")
                print("Local path for KEY file = \(filePath) ")
                if !FileManager.default.fileExists(atPath: filePath.path) {
                    print("KEY file is not exist, download and save to: \(filePath) ")
                    let success = FileManager.default.createFile(atPath: filePath.path, contents: dataKey, attributes: nil)
                    if success {
                        print("key write to file Succeeded")
                    } else {
                        print("key file write to file Failed! =\(dataKey.count)")
                    }
                }
            } else {
                print("KEY file download failed")
            }
        }
    }
    private func tryToGetKeyData(_ keyLastUrl: String, tsUrlRoots: [String]) -> Data? {
        //如果key路径非全路径
        if !(keyLastUrl.hasPrefix("http://") || keyLastUrl.hasPrefix("https://")) {
            /// 尝试去拼接路径下载key,如果下载成功，则表示路径拼对了，就直接返回keyData
            for tsUrlHeader in tsUrlRoots {
                var maybeRightUrl = ""
                if keyLastUrl.hasPrefix("/") {
                    maybeRightUrl = String(format: "%@%@", tsUrlHeader,keyLastUrl)
                } else {
                    maybeRightUrl = String(format: "%@/%@", tsUrlHeader,keyLastUrl)
                }
                if let keyData = try? Data(contentsOf: URL(string: maybeRightUrl)!) {
                    return keyData
                }
            }
        }else{
            //如果是全路径就直接下载
            if let keyData = try? Data(contentsOf: URL(string: keyLastUrl)!) {
                return keyData
            }
        }
        return nil
    }
    
    /// 尝试去拼接路径下载第一个ts,如果下载成功，则表示路径拼对了，将正确路径的前缀返给每个Ts model
    private func tryToGetRightTsPath(_ tsLastUrl: String, tsUrlRoots: [String]) -> String? {
        for tsUrlHeader in tsUrlRoots {
            var maybeRightUrl = ""
            if tsLastUrl.hasPrefix("/") {
                maybeRightUrl = String(format: "%@%@", tsUrlHeader,tsLastUrl)
            } else {
                maybeRightUrl = String(format: "%@/%@", tsUrlHeader,tsLastUrl)
            }
            if let _ = try? Data(contentsOf: URL(string: maybeRightUrl)!) {
                print("correct Ts downloadUrl = \(maybeRightUrl)")
                return tsUrlHeader
            } else {
                print("current TS download url is not correct.:\(maybeRightUrl)")
            }
        }
        return nil
    }
    
    /// 拆分m3u8文件，取出ts 和秘钥等
    private func sepalateRealM3u8List(_ m3u8Content: String, _ url: String, tsUrlRoots: [String]) -> [M3u8TsModel] {
        var tsModels: [M3u8TsModel] = []
        self.downLoadKeyWith(m3u8Content, tsUrlRoots: tsUrlRoots)
      
        let segmentRange = m3u8Content.range(of: "#EXTINF:")!
        let segmentsString = String(m3u8Content.suffix(from: segmentRange.lowerBound)).components(separatedBy: "#EXT-X-ENDLIST")
        var segmentArray = segmentsString[0].components(separatedBy: "\n")
        segmentArray = segmentArray.filter { !$0.contains("#EXT-X-DISCONTINUITY") && !$0.contains("#EXT-X-PRIVINF") }
        var rightTsUrl = ""
        if segmentArray.count > 2 {
            let segmentURL = segmentArray[1]
            /// ts 路径非全路径
            if !(segmentURL.hasPrefix("http://") || segmentURL.hasPrefix("https://")) {
                // 拼接url 全路劲
                rightTsUrl = self.tryToGetRightTsPath(segmentURL, tsUrlRoots: tsUrlRoots) ?? ""
                if rightTsUrl.isEmpty {
                    print("The correct ts download url was not found, maybe download failed.")
                    //return
                }
            }
        }
        
        /// 分割 m3u8文件，拼接TS model
        while (segmentArray.count > 2) {
            let segmentDurationPart = segmentArray[0].components(separatedBy: ":")[1]
            var segmentDuration: Float = 0.0
            
            if segmentDurationPart.contains(",") {
                segmentDuration = Float(segmentDurationPart.components(separatedBy: ",")[0])!
            } else {
                segmentDuration = Float(segmentDurationPart)!
            }
            var segmentURL = segmentArray[1]
            /// ts 路径非全路径
            if !(segmentURL.hasPrefix("http://") || segmentURL.hasPrefix("https://")) {
                // 拼接url 全路劲
                if segmentURL.hasPrefix("/") {
                    segmentURL = String(format: "%@%@", rightTsUrl,segmentURL)
                } else {
                    segmentURL = String(format: "%@/%@", rightTsUrl,segmentURL)
                }
            }
            let segmentModel = M3u8TsModel(duration: segmentDuration, url: segmentURL)
            tsModels.append(segmentModel)
            segmentArray.remove(at: 0)
            segmentArray.remove(at: 0)
        }
        return tsModels
    }
    
    /// 获取解密字符串
    ///
    /// - Returns: 解密字符串IV
    private func getIV(_ m3u8Content: String) -> String? {
        if !m3u8Content.contains("#EXT-X-KEY:") { return nil }
        // 用正则表达式取出秘钥所在 url
        let m3u8Pes = m3u8Content.components(separatedBy: "\n")
        var keyM3u8 = ""
        for pes in m3u8Pes {
            if pes.contains("IV=") && pes.contains("#EXT-X-KEY:") {
                keyM3u8 = pes.components(separatedBy: "IV=").last ?? ""
            }
        }
        if !keyM3u8.isEmpty {
            return keyM3u8
        }
        return nil
    }
    
    /// 创建本地M3u8文件，播放要用
    private func createLocalM3U8file(_ m3u8Content: String, tsList: [M3u8TsModel]) {
        
        let filePath = self.downloadPath.appendingPathComponent("\(self.identifier).m3u8")
        
        /// 解密的key 所在的路径和ts视频片段在同一文件目录下，所以这里直接用相对路径，如果不在一个文件夹下，需要拼接绝对路径
        let keyPath = "key.key"
        ///绝对路径
        let keyPathAll = self.downloadPath.appendingPathComponent("key.key")
        var header = "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:60\n"
        if m3u8Content.contains("#EXT-X-KEY:") && FileManager.default.fileExists(atPath: keyPathAll.path) {
            var keyStringPath = String(format: "#EXT-X-KEY:METHOD=AES-128,URI=\"%@\"", keyPath)
            if let iv = getIV(m3u8Content) {
                keyStringPath = String(format: "#EXT-X-KEY:METHOD=AES-128,URI=\"%@\",IV=%@", keyPath,iv)
            }
            header = String(format: "#EXTM3U\n#EXT-X-VERSION:3\n#EXT-X-TARGETDURATION:60\n%@\n", keyStringPath)
        }
        var content = ""
        
        for i in 0 ..< tsList.count {
            let segmentModel = tsList[i]
            let length = "#EXTINF:\(segmentModel.duration),\n"
            let fileName = "\(segmentModel.filename).ts\n"
            content += (length + fileName)
        }
        
        header.append(content)
        header.append("#EXT-X-ENDLIST\n")
        
        let writeData: Data = header.data(using: .utf8)!
        try! writeData.write(to: filePath)
    }
    
    /// 根据正则表达式  截取字符串 ：pattern： 正则字符串  str: 被截取字符串
    private func regexGetSub(pattern: String, str: String) -> String {
        let regex = try! NSRegularExpression(pattern: pattern, options:[])
        let matches = regex.matches(in: str, options: [], range: NSRange(str.startIndex...,in: str))
        if matches.count > 0 {
            let ss = str[Range(matches[0].range(at: 1), in: str)!]
            let sttss = ss.components(separatedBy: "\"")
            if sttss.count>0 {
                return sttss.first ?? ""
            }
        }
        return ""
    }
}
